﻿#nullable enable

using System;
using System.Collections.Generic;
using System.Reflection;
using KSP.Localization;
using UnityEngine;

namespace MuMech
{
    public class ManeuverParameters
    {
        // ReSharper disable once InconsistentNaming
        public Vector3d dV;
        public double   UT;

        public ManeuverParameters(Vector3d dV, double ut)
        {
            this.dV = dV;
            UT      = ut;
        }
    }

    public class OperationException : Exception
    {
        public OperationException(string message) : base(message) { }
    }

    public abstract class Operation
    {
        protected string ErrorMessage = "";
        public    string GetErrorMessage() => ErrorMessage;

        // Methods that need to be implemented for new operations:

        // Description that will be displayed on the GUI
        public abstract string GetName();

        // Draw the parameter part of the Operation (ask for time, altitudes etc)
        // Input parameters are orbit and time parameters after the last maneuver and current target
        public abstract void DoParametersGUI(Orbit o, double universalTime, MechJebModuleTargetController target);

        // Function called when create node is pressed; input parameters are orbit and time parameters after the last maneuver and current target
        // ManeuverParameters contain a single time and dV describing the node that should be executed
        // In case of error you can throw an OperationException, the message will be displayed and no node will be created.
        protected abstract List<ManeuverParameters>? MakeNodesImpl(Orbit o, double universalTime, MechJebModuleTargetController target);

        public List<ManeuverParameters>? MakeNodes(Orbit o, double universalTime, MechJebModuleTargetController target)
        {
            ErrorMessage = "";
            try
            {
                return MakeNodesImpl(o, universalTime, target);
            }
            catch (OperationException e)
            {
                ErrorMessage = e.Message;
                return null;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                ErrorMessage = Localizer.Format("#MechJeb_Maneu_errorMessage"); //An error occurred while creating the node.
                return null;
            }
        }

        private static readonly List<Type> _operations = new List<Type>();

        private static void AddTypes(Type[] types)
        {
            foreach (Type t in types)
            {
                if (t is { IsAbstract: false }
                    && typeof(Operation).IsAssignableFrom(t)
                    && t.GetConstructor(Type.EmptyTypes) != null)
                    _operations.Add(t);
            }
        }

        public static Operation[] GetAvailableOperations()
        {
            // Use reflection to discover all classes that inherit from Operation and have a defalt constructor
            if (_operations.Count == 0)
            {
                foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        AddTypes(assembly.GetTypes());
                    }
                    catch (ReflectionTypeLoadException e)
                    {
                        AddTypes(e.Types);
                    }
                    catch (InvalidOperationException)
                    {
                        // Silently drop exception generated by users who manage to put assembly that
                        // can't load for reasons (missing deps most of the time)
                    }
                }

                Debug.Log("ManeuverPlanner initialization: found " + _operations.Count + " maneuvers");
            }

            List<Operation> res = _operations.ConvertAll(t => (Operation)t.GetConstructor(Type.EmptyTypes)!.Invoke(null));
            res.Sort((x, y) => string.Compare(x.GetName(), y.GetName(), StringComparison.Ordinal));
            return res.ToArray();
        }

        public virtual bool Draggable => true;
    }
}
